package com.mkx.core.wd;
import com.mkx.core.WatchDog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 简单时间轮实现
 * 注意：这是一个简化版的时间轮，生产环境建议使用成熟的时间轮实现如Netty的HashedWheelTimer
 */
public class TimeWheelWatchDog implements WatchDog {
    
    private static final Logger logger = LoggerFactory.getLogger(TimeWheelWatchDog.class);
    
    private final RedisTemplate<String, Object> redisTemplate;
    private final String lockKey;
    private final String lockValue;
    private final long renewInterval;
    private final long tickDuration;
    
    private volatile Thread workerThread;
    private final AtomicBoolean running = new AtomicBoolean(false);
    private long lastRenewTime;
    
    private static final String RENEW_SCRIPT = """
        -- 增加对nil值的额外检查，提高集群环境下的鲁棒性
        -- 使用Lua脚本确保续期操作的原子性
        local currentValue = redis.call('get', KEYS[1])
        if currentValue == ARGV[1] then
            -- 锁归当前客户端所有，可以续期
            return redis.call('pexpire', KEYS[1], ARGV[2])
        elseif currentValue == nil then
            -- 锁已不存在，返回0表示续期失败
            return 0
        else
            -- 锁被其他客户端持有，返回0表示续期失败
            return 0
        end
        """;
    
    public TimeWheelWatchDog(RedisTemplate<String, Object> redisTemplate, 
                            String lockKey, 
                            String lockValue, 
                            long renewInterval,
                            long tickDuration) {
        this.redisTemplate = redisTemplate;
        this.lockKey = lockKey;
        this.lockValue = lockValue;
        this.renewInterval = renewInterval;
        this.tickDuration = tickDuration;
        this.lastRenewTime = System.currentTimeMillis();
    }
    
    @Override
    public void start() {
        if (running.compareAndSet(false, true)) {
            workerThread = Thread.ofVirtual().name("redis-watchdog-timewheel-" + lockKey).start(() -> {
                logger.debug("TimeWheel WatchDog started for lock: {}", lockKey);
                
                while (running.get()) {
                    try {
                        long currentTime = System.currentTimeMillis();
                        if (currentTime - lastRenewTime >= renewInterval) {
                            renewLock();
                            lastRenewTime = currentTime;
                        }
                        
                        // 睡眠一个tick时间
                        Thread.sleep(tickDuration);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;
                    } catch (Exception e) {
                        logger.error("Error in TimeWheel WatchDog for lock: {}", lockKey, e);
                    }
                }
                
                logger.debug("TimeWheel WatchDog stopped for lock: {}", lockKey);
            });
        }
    }
    
    @Override
    public void stop() {
        if (running.compareAndSet(true, false)) {
            if (workerThread != null) {
                workerThread.interrupt();
                try {
                    workerThread.join(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
    
    @Override
    public boolean isRunning() {
        return running.get();
    }
    
    private void renewLock() {
        try {
            var script = new DefaultRedisScript<Long>();
            script.setScriptText(RENEW_SCRIPT);
            script.setResultType(Long.class);
            
            Long result = redisTemplate.execute(script, 
                Collections.singletonList(lockKey), 
                lockValue, 
                String.valueOf(renewInterval * 2));
            
            if (result != null && result == 1) {
                logger.trace("Lock renewed by TimeWheel WatchDog: {}", lockKey);
            } else {
                logger.warn("Lock renewal failed by TimeWheel WatchDog, maybe released: {}", lockKey);
                this.stop();
            }
        } catch (Exception e) {
            logger.error("Error renewing lock by TimeWheel WatchDog: {}", lockKey, e);
            this.stop();
        }
    }
}